package com.summerbird.mallchat.common.user.service.impl;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.summerbird.mallchat.common.common.annotation.DoubleCache;
import com.summerbird.mallchat.common.common.event.UserBlackEvent;
import com.summerbird.mallchat.common.common.event.UserRegisterEvent;
import com.summerbird.mallchat.common.common.event.listener.UserRegisterListener;
import com.summerbird.mallchat.common.common.utils.AssertUtil;
import com.summerbird.mallchat.common.common.utils.sensitiveWord.SensitiveWordBs;
import com.summerbird.mallchat.common.user.dao.*;
import com.summerbird.mallchat.common.user.domain.dao.ItemInfoDTO;
import com.summerbird.mallchat.common.user.domain.dao.SummeryInfoDTO;
import com.summerbird.mallchat.common.user.domain.entity.*;
import com.summerbird.mallchat.common.user.domain.enums.BlackTypeEnum;
import com.summerbird.mallchat.common.user.domain.enums.ItemEnum;
import com.summerbird.mallchat.common.user.domain.enums.ItemTypeEnum;
import com.summerbird.mallchat.common.user.domain.vo.req.BlackReq;
import com.summerbird.mallchat.common.user.domain.vo.req.ItemInfoReq;
import com.summerbird.mallchat.common.user.domain.vo.req.SummeryInfoReq;
import com.summerbird.mallchat.common.user.domain.vo.req.WearingBadgeReq;
import com.summerbird.mallchat.common.user.domain.vo.resp.BadgeResp;
import com.summerbird.mallchat.common.user.domain.vo.resp.UserInfoResp;
import com.summerbird.mallchat.common.user.service.ItemConfigService;
import com.summerbird.mallchat.common.user.service.UserService;
import com.summerbird.mallchat.common.user.service.adapter.UserAdapter;
import com.summerbird.mallchat.common.user.service.cache.ItemCache;
import com.summerbird.mallchat.common.user.service.cache.UserCache;
import com.summerbird.mallchat.common.user.service.cache.UserSummaryCache;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;
    @Autowired
    private UserBackpackDao userBackpackDao;
    @Autowired
    private ItemCache itemCache;
    @Autowired
    private UserCache userCache;
    @Autowired
    private UserSummaryCache userSummaryCache;
    @Autowired
    private ItemConfigDao itemConfigDao;
    @Autowired
    private UserRegisterListener userRegisterListener;
    @Autowired
    private BlackDao blackDao;
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;
    @Autowired
    private SensitiveWordBs sensitiveWordBs;

    @Override
    @Transactional
    public Long register(User insert) {
        userDao.save(insert);
        // 用户注册事件
        userRegisterListener.sendCard(new UserRegisterEvent(this, insert));
        // 注册用户徽章
        userRegisterListener.sendBadge(new UserRegisterEvent(this, insert));
        return insert.getId();
    }

    @Override
    public UserInfoResp getUserInfoById(Long uid) {
        User user = userDao.getById(uid);
        Integer modifyNameChance = userBackpackDao.getCountByValidItemId(uid, ItemEnum.MODIFY_NAME_CARD.getId());
        return UserAdapter.buildUserInfo(user, modifyNameChance);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void modifyName(Long uid, String name) {
        AssertUtil.isFalse(sensitiveWordBs.hasSensitiveWord(name), "名字中包含敏感词，请重新输入");
        // 1. 名字是否重复
        User oldUser = userDao.getByName(name);
        AssertUtil.isEmpty(oldUser, "名字已经被抢占，请换一个~");
        // 2. 是否有改名卡
        UserBackpack modifyNameItem = userBackpackDao.getFirstValidItem(uid, ItemEnum.MODIFY_NAME_CARD.getId());
        AssertUtil.isNotEmpty(modifyNameItem, "改名卡数量不足了哦~等后续活动获取吧！");
        // 3. 改名并消耗改名卡
        boolean success = userBackpackDao.useItem(modifyNameItem);
        if(success){
            // 改名
            userDao.modifyName(uid, name);
            //删除缓存
            userCache.userInfoChange(uid);
        }
    }

    @Override

    public List<BadgeResp> badges(Long uid) {
        // 1. 获取所有徽章
        List<ItemConfig> itemConfigs = itemCache.getByType(ItemTypeEnum.BADGE.getType());
        // 2. 查看用户所有徽章
        List<UserBackpack> userBackpacks = userBackpackDao.getByItemIds(uid, itemConfigs.stream().map(ItemConfig::getId).collect(Collectors.toList()));
        // 3. 查看用户佩戴的徽章
        User user = userDao.getById(uid);
        return UserAdapter.buildBadgeResp(itemConfigs, userBackpacks, user);
    }

    @Override
    public void wearingBadge(Long uid, Long itemId) {
        // 确保有徽章
        UserBackpack userBackpack = userBackpackDao.getFirstValidItem(uid, itemId);
        AssertUtil.isNotEmpty(userBackpack, "您还没有该徽章哦~");
        // 确保该物品是徽章
        ItemConfig itemConfig = itemConfigDao.getById(itemId);
        AssertUtil.equal(itemConfig.getType(), ItemTypeEnum.BADGE.getType(), "只有徽章才能佩戴哦~");
        userDao.wearingBadge(uid, itemId);
        // 删除缓存
        userCache.userInfoChange(uid);
    }

    @Override
    @Transactional
    public void blackUser(BlackReq blackReq) {
        Long uid = blackReq.getId();
        // 禁用该用户uid
        Black black = new Black();
        black.setType(BlackTypeEnum.UID.getType());
        black.setTarget(uid.toString());
        blackDao.save(black);
        // 禁用该用户的ip
        User user = userDao.getById(uid);
        blackIp(Optional.ofNullable(user.getIpInfo()).map(IpInfo::getCreateIp).get());
        blackIp(Optional.ofNullable(user.getIpInfo()).map(IpInfo::getUpdateIp).get());
        // 发送ws用户警用事件
        applicationEventPublisher.publishEvent(new UserBlackEvent(this, user));
    }

    @Override
    public List<SummeryInfoDTO> getSummeryUserInfo(SummeryInfoReq req) {
        List<Long> needSyncUidList = getNeedSyncUidList(req.getReqList());
        Map<Long, SummeryInfoDTO> batch = userSummaryCache.getBatch(needSyncUidList);
        return req.getReqList()
                .stream()
                .map(infoReq -> batch.containsKey(infoReq.getUid()) ? batch.get(infoReq.getUid()) : SummeryInfoDTO.skip(infoReq.getUid()))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    private List<Long> getNeedSyncUidList(List<SummeryInfoReq.infoReq> reqList) {
        ArrayList<Long> needSyncUidList = new ArrayList<>();
        List<Long> userModifyTime = userCache.getModifyTime(reqList.stream().map(SummeryInfoReq.infoReq::getUid).collect(Collectors.toList()));
        for (int i = 0; i < reqList.size(); i++) {
            Long lastModifyTime = reqList.get(i).getLastModifyTime();
            Long modifyTime = userModifyTime.get(i);
            if(Objects.isNull(lastModifyTime) || Objects.nonNull(modifyTime) && modifyTime > lastModifyTime){
                needSyncUidList.add(reqList.get(i).getUid());
            }
        }
        return needSyncUidList;
    }

    @Override
    public List<ItemInfoDTO> getItemInfo(ItemInfoReq req) {
        return req.getReqList().stream().map(a -> {
            ItemConfig itemConfig = itemCache.getById(a.getItemId());
            if (Objects.nonNull(a.getLastModifyTime()) && a.getLastModifyTime() >= itemConfig.getUpdateTime().getTime()) {
                return ItemInfoDTO.skip(a.getItemId());
            }
            ItemInfoDTO dto = new ItemInfoDTO();
            dto.setItemId(itemConfig.getId());
            dto.setImg(itemConfig.getImg());
            dto.setDescribe(itemConfig.getDescribe());
            return dto;
        }).collect(Collectors.toList());
    }

    private void blackIp(String ip) {
        if(StringUtils.isBlank(ip)){
            return;
        }
        // 禁用的ip不可重复
        Black black = blackDao.getByIp(ip);
        if(Objects.nonNull(black)){
            return;
        }
        black = new Black();
        black.setType(BlackTypeEnum.IP.getType());
        black.setTarget(ip);
        blackDao.save(black);
    }
}
